#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include "semaphore.h"

#define SHM_FILE       "./shmfile"
#define SHM_PAGE_NUM    1 // 1page 4K
#define SEM_NUM         2 // 信号量数量


static int semid = -1;
static int shmid = -1;
static void *shmaddr = NULL;


static void print_err(char *str) {
    perror(str);
    exit(-1);
}

int create_or_get_shm(int nsize) {
    // 确保路径文件一定存在，如果不存在就创建
    int fd = open(SHM_FILE, O_RDWR | O_CREAT, 0664);
    if (fd < 0) print_err("open fail");

    // 根据指定路径和编号，创建key
    key_t key = ftok(SHM_FILE, 'a');
    if (key < 0) print_err("key fail");

    // 根据key创建或获取shmid（共享内存id），大小一般是4K（一页）整数倍
    int shmid = shmget(key, nsize * 4096 , 0664 | IPC_CREAT);
    if (shmid < 0) print_err("shmget fail");

    // 关闭已打开文件
    close(fd);

    return shmid;
}

void signal_fun(int signo) {
    if (signo == SIGINT || signo == SIGTERM) {
        if (semid > 0) sem_destroy(semid);
        if (shmaddr != NULL)  shmdt(shmaddr);

        if (shmid > 0) shmctl(shmid, IPC_RMID, NULL);
        
        remove(SHM_FILE); // 移除用于创建shm的临时文件(路径)
    }
}


/**
 * 进程B
 * 进程B通过share memory接收进程A传输的数据,通过信号量pv操作等待数据 */
int main() {
    signal(SIGINT, signal_fun);
    signal(SIGTERM, signal_fun);

    // 创建或获取共享内存ID
    shmid = create_or_get_shm(SHM_PAGE_NUM);

    // 建立映射
    shmaddr = shmat(shmid, NULL, 0); // NULL OS自动分配页对齐地址,映射到内存空间
    if (shmaddr == (void *)-1) print_err("shmat fail");


    semid = sem_get(SEM_NUM); // 创建信号量集
    printf("semid = %d\n", semid);

    int semnum[1] = {0}; // 存放信号量编号
    /* 初始化信号量 */
    for (int i = 0; i < SEM_NUM; ++i) {
        if (i == 0) sem_init(semid, i, 1);
        else sem_init(semid, i, 0);
    }

    printf("process B is ready.\n");

    while (1) {
	printf("p s1\n");
        semnum[0] = 1;
        sem_p(semid, semnum, 1); // p sem 1
        
        // 从共享内存读取数据并打印
        printf("recv: %s\n", (char *)shmaddr);
        memset(shmaddr, 0, SHM_PAGE_NUM * 4096); // 清除共享内存内容

        semnum[0] = 0;
        sem_v(semid, semnum, 1); // v sem 0
	printf("v s0\n");
    }

    return 0;
}

